package parserxml;

import java.io.*;
import java.util.*;
import net.n3.nanoxml.*;

/**
 *
 * @author Jacob
 */
public class GeneradorScriptInserciones {

    HashMap<String, Tabla> tablasSQL;
    HashMap<String, RelacionER> relacionesER;

    public GeneradorScriptInserciones(HashMap<String, Tabla> tablasSQL,
            HashMap<String, RelacionER> relacionesER) {
        this.tablasSQL = tablasSQL;
        this.relacionesER = relacionesER;
    }

    public void generarDeArchivo(String nombreArchXML) throws Exception {

        IXMLParser parser = XMLParserFactory.createDefaultXMLParser();
        IXMLReader reader = StdXMLReader.fileReader(nombreArchXML);
        parser.setReader(reader);
        IXMLElement raiz_xml = (IXMLElement) parser.parse();

        Enumeration<IXMLElement> hijos_raiz_xml = raiz_xml.enumerateChildren();
        IXMLElement hijo_xml;
        while (hijos_raiz_xml.hasMoreElements()) {
            hijo_xml = hijos_raiz_xml.nextElement();
            // chequeo si la relacion existe en mis estructuras
            if (relacionesER.containsKey(hijo_xml.getName())) {
                procesarRelacionXML(hijo_xml);
            } else if (tablasSQL.containsKey(hijo_xml.getName())) {
                procesarEntidadXML(hijo_xml, null);
            } else {
                // TODO error, el elemento xml no corresponde con relaciones o entidades
            }
        }
    }

    public void generarDeTexto(String textoXML) throws Exception {

        IXMLParser parser = XMLParserFactory.createDefaultXMLParser();
        IXMLReader reader = StdXMLReader.stringReader(textoXML);
        parser.setReader(reader);
        IXMLElement raiz_xml = (IXMLElement) parser.parse();

        Enumeration<IXMLElement> hijos_raiz_xml = raiz_xml.enumerateChildren();
        IXMLElement hijo_xml;
        while (hijos_raiz_xml.hasMoreElements()) {
            hijo_xml = hijos_raiz_xml.nextElement();
            // chequeo si la relacion existe en mis estructuras
            if (relacionesER.containsKey(hijo_xml.getName())) {
                procesarRelacionXML(hijo_xml);
            } else if (tablasSQL.containsKey(hijo_xml.getName())) {
                procesarEntidadXML(hijo_xml, null);
            } else {
                // TODO error, el elemento xml no corresponde con relaciones o entidades
            }
        }
    }

    private void procesarRelacionXML(IXMLElement relacionXML) {

        RelacionER relacionER = relacionesER.get(relacionXML.getName());
        if (relacionER.getAbsorbida() == null) {
            insertarRelacionNoAbsorbida(relacionER, relacionXML);
        } else {
            insertarRelacionAbsorbida(relacionER, relacionXML);
        }
    }

    private void insertarRelacionNoAbsorbida(RelacionER relacionER,
            IXMLElement relacionXML) {

        Tabla relacionSQL = tablasSQL.get(relacionER.getNombreRelacion());
        HashMap<String, String> filaSQL = new HashMap<String, String>();

        Enumeration enuHijosXML = relacionXML.enumerateChildren();
        IXMLElement hijoXML;
        while (enuHijosXML.hasMoreElements()) {
            hijoXML = (IXMLElement) enuHijosXML.nextElement();
            if (relacionER.hasEntidad(hijoXML.getName())) {
                // hijoXML es entidad
                // TODO chequear errores de participacion antes de agregar la entidad

                procesarEntidadXML(hijoXML, null);

                // inserto la referencia a la entidad en la fila de la tabla relacion
                Tabla entidadSQL = tablasSQL.get(hijoXML.getName());
                String nombreClave = entidadSQL.getClavePK().iterator().next();
                filaSQL.put(nombreClave, hijoXML.getAttribute(nombreClave, ""));
            } else if (relacionER.hasAtributo(hijoXML.getName())) {
                // hijoXML es atributo
                filaSQL.put(hijoXML.getName(), hijoXML.getContent());
            } else {
                // TODO error, el elemento no corresponde a la relacion
            }
        }
        // agrego la fila a la tabla
        relacionSQL.addFila(filaSQL);
    }

    private void insertarRelacionAbsorbida(RelacionER relacionER,
            IXMLElement relacionXML) {

        Tabla tablaAbsorbio = tablasSQL.get(relacionER.getAbsorbida());
        IXMLElement entidadXMLAbsorbio =
                relacionXML.getFirstChildNamed(relacionER.getAbsorbida());

        if (entidadXMLAbsorbio == null) {
            // TODO error, falta entidad
            return;
        }

        String nombreClave = tablaAbsorbio.getClavePK().iterator().next();
        String valorClave = entidadXMLAbsorbio.getAttribute(nombreClave, "");
        HashMap<String, String> filaSQL =
                tablaAbsorbio.getEntidadConClave(nombreClave, valorClave);

        if (filaSQL == null) {
            // la entidadXMLAbsorbio no existe en la tabla, creo una nueva fila
            filaSQL = new HashMap<String, String>();
            procesarEntidadXML(entidadXMLAbsorbio, filaSQL);
        }

        Enumeration enuHijosXML = relacionXML.enumerateChildren();
        IXMLElement hijoXML;
        while (enuHijosXML.hasMoreElements()) {
            hijoXML = (IXMLElement) enuHijosXML.nextElement();
            if (tablaAbsorbio.getNombre().equals(hijoXML.getName())) {
                // ya se proceso antes
                continue;
            } else if (relacionER.hasEntidad(hijoXML.getName())) {
                // hijoXML es entidad
                procesarEntidadXML(hijoXML, null);

                // inserto la referencia a la entidad en la fila de la tabla que absorbio
                Tabla entidadSQL = tablasSQL.get(hijoXML.getName());
                nombreClave = entidadSQL.getClavePK().iterator().next();
                filaSQL.put(nombreClave, hijoXML.getAttribute(nombreClave, ""));
            } else if (relacionER.hasAtributo(hijoXML.getName())) {
                // hijoXML es atributo
                filaSQL.put(hijoXML.getName(), hijoXML.getContent());
            } else {
                // TODO error, el elemento no corresponde a la relacion
            }
        }
    }

    private void procesarEntidadXML(IXMLElement entidadXML,
            HashMap<String, String> filaSQL) {

        if (filaSQL == null) {
            filaSQL = new HashMap<String, String>();
        }

        Tabla tablaSQL = tablasSQL.get(entidadXML.getName());

        // Cheque si la entidad ya existe en la tabla
        String nombreClave = tablaSQL.getClavePK().iterator().next();
        String valorClave = entidadXML.getAttribute(nombreClave, null);
        if (tablaSQL.getEntidadConClave(nombreClave, valorClave) != null) {
            // La entidad ya existe en la tabla
            return;
        }

        // agrego los atributos del elemento XML
        insertarAtributosXML(entidadXML.getAttributes(), tablaSQL, filaSQL);

        if (entidadXML.hasChildren()) {
            procesarHijosXMLEntidad(entidadXML, tablaSQL, filaSQL, entidadXML.getName());
        } else {
            filaSQL.put(entidadXML.getName(), entidadXML.getContent());
        }

        tablaSQL.addFila(filaSQL);
    }

    private void insertarAtributosXML(Properties attributes, Tabla tablaSQL,
            HashMap<String, String> filaSQL) {

        Enumeration nombresAtributos = attributes.propertyNames();
        String nombreAtributo;
        String valorAtributo;
        while (nombresAtributos.hasMoreElements()) {
            nombreAtributo = (String) nombresAtributos.nextElement();
            if (tablaSQL.hasAtributo(nombreAtributo)) {
                valorAtributo = attributes.getProperty(nombreAtributo);
                filaSQL.put(nombreAtributo, valorAtributo);
            } else {
                // TODO error, el atributo no es parte de la tabla
            }
        }
    }

    private void procesarHijosXMLEntidad(IXMLElement hijoXML, Tabla tablaPadre,
            HashMap<String, String> filaPadre, String sufijo) {

        Enumeration hijosXML = hijoXML.enumerateChildren();
        IXMLElement hijoXMLAux;
        while (hijosXML.hasMoreElements()) {
            hijoXMLAux = (IXMLElement) hijosXML.nextElement();
            procesarHijoXMLEntidad(hijoXMLAux, tablaPadre, filaPadre, sufijo);
        }
    }

    private void procesarHijoXMLEntidad(IXMLElement hijoXML, Tabla tablaPadre,
            HashMap<String, String> filaPadre, String sufijo) {

        if (tablaPadre.hasAtributo(hijoXML.getName())) {
            filaPadre.put(hijoXML.getName(), hijoXML.getContent());

        } else if (tablasSQL.containsKey(hijoXML.getName() + "_" + sufijo)
                || tablasSQL.containsKey(hijoXML.getName())) {
            // hijoXML es multivaluado

            // creo una nueva fila para la tabla hijo
            HashMap<String, String> filaHijo = new HashMap<String, String>();
            // coloco las referencias al padre
            Iterator<String> clavesPKTablaPadre = tablaPadre.getClavePK().iterator();
            String clavePK;
            while (clavesPKTablaPadre.hasNext()) {
                clavePK = clavesPKTablaPadre.next();
                filaHijo.put(clavePK, filaPadre.get(clavePK));
            }

            Tabla tablaHijo = tablasSQL.get(hijoXML.getName() + "_" + sufijo);
            if (tablaHijo == null) {
                tablaHijo = tablasSQL.get(hijoXML.getName());
            }

            if (hijoXML.hasChildren()) {
                procesarHijosXMLEntidad(hijoXML, tablaHijo, filaHijo, hijoXML.getName() + "_" + sufijo);
            } else {
                filaHijo.put(hijoXML.getName(), hijoXML.getContent());
            }

            tablaHijo.addFila(filaHijo);

        } else if (hijoXML.hasChildren()) {
            // en caso de que se hayan aplanado los atributos
            procesarHijosXMLEntidad(hijoXML, tablaPadre, filaPadre, hijoXML.getName() + "_" + sufijo);
        }
    }

    public void escribirScriptEnArchivo(String nombreArchSQL) {

        Writer archSQL;
        try {
            archSQL = new FileWriter(nombreArchSQL);
            archSQL.write("SET ESCAPE OFF\nSET DEFINE OFF\n\n");
        } catch (IOException ex) {
            System.err.println("Error al abrir archivo de esquema: " + nombreArchSQL);
            return;
        }


        Iterator<Tabla> iteTablas = tablasSQL.values().iterator();
        Tabla tabla;
        while (iteTablas.hasNext()) {
            tabla = iteTablas.next();

            Iterator<HashMap<String, String>> iteFilas = tabla.getFilas();
            HashMap<String, String> filaSQL;
            while (iteFilas.hasNext()) {
                filaSQL = iteFilas.next();
                String columns = "INSERT INTO " + tabla.getNombre() + " (";
                String values = "VALUES (";

                Iterator<String> columnaSQL = filaSQL.keySet().iterator();
                String nombreColumna;
                while (columnaSQL.hasNext()) {
                    nombreColumna = columnaSQL.next();
                    columns += nombreColumna;
                    if (filaSQL.get(nombreColumna) == null) {
                        values += "''";
                    } else {
                        values += "'" + filaSQL.get(nombreColumna).replace("'", "''") + "'";
                    }
                    if (columnaSQL.hasNext()) {
                        columns += ", ";
                        values += ", ";
                    }
                }
                try {
                    archSQL.write(columns + ")\n");
                    archSQL.write(values + ");\n");
                } catch (IOException ex) {
                    System.err.println("Error al escribir en archivo de esquema: " + nombreArchSQL);
                }
            }
            // Termina la tabla
            try {
                archSQL.write("\n");
            } catch (IOException ex) {
                System.err.println("Error al escribir en archivo de esquema: " + nombreArchSQL);
            }
        }

        // Cierro el archivo
        try {
            archSQL.write("SET ESCAPE ON\nSET DEFINE ON\n\n");
            archSQL.close();
        } catch (IOException ex) {
            System.err.println("Error al cerrar archivo de esquema: " + nombreArchSQL);
        }
    }

    public String escribirScriptEnString() {

        String salida = "SET ESCAPE OFF\nSET DEFINE OFF\n\n";

        Iterator<Tabla> iteTablas = tablasSQL.values().iterator();
        Tabla tabla;
        while (iteTablas.hasNext()) {
            tabla = iteTablas.next();

            Iterator<HashMap<String, String>> iteFilas = tabla.getFilas();
            HashMap<String, String> filaSQL;
            while (iteFilas.hasNext()) {
                filaSQL = iteFilas.next();
                String columns = "INSERT INTO " + tabla.getNombre() + " (";
                String values = "VALUES (";

                Iterator<String> columnaSQL = filaSQL.keySet().iterator();
                String nombreColumna;
                while (columnaSQL.hasNext()) {
                    nombreColumna = columnaSQL.next();
                    columns += nombreColumna;
                    if (filaSQL.get(nombreColumna) == null) {
                        values += "''";
                    } else {
                        values += "'" + filaSQL.get(nombreColumna).replace("'", "''") + "'";
                    }
                    if (columnaSQL.hasNext()) {
                        columns += ", ";
                        values += ", ";
                    }
                }
                salida += columns + ")\n" + values + ");\n";
            }
            // Termina la tabla
            salida += "\n";
        }

        // Cierro el archivo
        return salida + "SET ESCAPE ON\nSET DEFINE ON\n\n";
    }

    public void escribirScriptEnVariosArchivos(String nombreArchSQLPadre) {

        Iterator<Tabla> iteTablas = tablasSQL.values().iterator();
        Tabla tabla;
        while (iteTablas.hasNext()) {
            tabla = iteTablas.next();

            // Creo el archivo para la tabla
            String nombreArchTablaSQL = nombreArchSQLPadre.substring(0,
                    nombreArchSQLPadre.lastIndexOf('.'))
                    + "_" + tabla.getNombre() + ".sql";
            Writer archSQL;
            try {
                archSQL = new FileWriter(nombreArchTablaSQL);
                archSQL.write("SET ESCAPE OFF\nSET DEFINE OFF\n\n");
            } catch (IOException ex) {
                System.err.println("Error al abrir archivo de esquema: " + nombreArchTablaSQL);
                return;
            }

            try {
                Iterator<HashMap<String, String>> iteFilas = tabla.getFilas();
                HashMap<String, String> filaSQL;
                while (iteFilas.hasNext()) {
                    filaSQL = iteFilas.next();
                    String columns = "INSERT INTO " + tabla.getNombre() + " (";
                    String values = "VALUES (";

                    Iterator<String> columnas = filaSQL.keySet().iterator();
                    String nombreColumna;
                    while (columnas.hasNext()) {
                        nombreColumna = columnas.next();
                        columns += nombreColumna;
                        if (filaSQL.get(nombreColumna) == null) {
                            values += "''";
                        } else {
                            values += "'" + filaSQL.get(nombreColumna).replace("'", "''") + "'";
                        }
                        if (columnas.hasNext()) {
                            columns += ", ";
                            values += ", ";
                        }
                    }
                    archSQL.write(columns + ")\n");
                    archSQL.write(values + ");\n");
                }
                archSQL.write("\n");

            } catch (IOException ex) {
                System.err.println("Error al escribir en archivo de esquema: " + nombreArchTablaSQL);
            }

            // Cierro el archivo de la tabla
            try {
                archSQL.write("SET ESCAPE ON\nSET DEFINE ON\n\n");
                archSQL.close();
            } catch (IOException ex) {
                System.err.println("Error al cerrar archivo de esquema: " + nombreArchTablaSQL);
            }
        }
    }
}
